package com.apobates.jforum.grief.aurl.executor;

import com.apobates.jforum.grief.aurl.core.AUrlMeta;
import com.apobates.jforum.grief.aurl.core.AUrlMetaBuilder;
import com.apobates.jforum.grief.aurl.spring.StringUtils;
import com.apobates.jforum.grief.aurl.executor.HClientContext;
import java.io.IOException;
import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
import java.util.function.Function;

/**
 * 内置的HttpClient执行器
 */
public class HClientExecutor extends AUrlMetaExecutor<HClientResponse<String>>{

    public HClientExecutor(AUrlMeta rawMeta) {
        super(rawMeta);
    }

    @Override
    public Set<HClientResponse<String>> getActionRecords() {
        return Collections.emptySet();
    }

    @Override
    public AUrlMetaBuilder getBuilder() {
        return getRawMeta().toBuilder();
    }

    /**
     * 创建请求客户端
     * @param config 请求上下文参数
     * @return
     * @param <T>
     */
    protected static <T> HttpClient buildClient(HClientContext<T> config){
        HttpClient.Builder client = HttpClient.newBuilder()
                .version(config.getVersion())
                .followRedirects(HttpClient.Redirect.NORMAL)
                .connectTimeout(config.getConnectTimeOut());
        if(config.getVersion() == HttpClient.Version.HTTP_2){
            client = client.priority(config.getPriority());
        }
        if(config.getExecutorService() != null){
            client = client.executor(config.getExecutorService());
        }
        if(config.getAuthenticator() != null){
            client = client.authenticator(config.getAuthenticator());
        }
        if(config.getProxy() != null){
            client = client.proxy(config.getProxy());
        }
        if(config.getCookieHandler() != null){
            client = client.cookieHandler(config.getCookieHandler());
        }
        if(config.getSslContext() != null){
            client = client.sslContext(config.getSslContext());
        }
        if(config.getSslParameters() != null){
            client = client.sslParameters(config.getSslParameters());
        }
        return client.build();
    }

    /**
     * 执行常规同步HEAD请求
     * @return
     */
    public HClientResponse<Void> head() throws URISyntaxException{
        return head(getRawMeta().toString(), HClientContext.defaultConfig);
    }

    /**
     * 执行常规同步GET请求
     * @return
     */
    public HClientResponse<String> get() throws URISyntaxException{
        return get(getRawMeta().toString(), HClientContext.defaultConfig);
    }

    /**
     * 执行常规同步GET JSON请求
     * @return
     */
    public HClientResponse<String> getJson()  throws URISyntaxException{
        return getJson(getRawMeta().toString());
    }

    /**
     * 执行常规同步Form POST请求
     * @param postForm 表单值
     * @return
     * @throws URISyntaxException
     */
    public HClientResponse<String> post(Map<String, String> postForm) throws URISyntaxException {
        return post(getRawMeta().toString(), postForm);
    }

    /**
     * 执行常规同步GET JSON请求
     *
     * @param url 请求url
     * @return
     */
    public static HClientResponse<String> getJson(String url) throws URISyntaxException {
        HClientContext<String> HClientContext = com.apobates.jforum.grief.aurl.executor.HClientContext.Builder.<String>of(1).contentType("application/json").bodyHandler(HttpResponse.BodyHandlers.ofString()).build();
        return get(url, HClientContext);
    }

    /**
     * 执行常规的GET请求
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @return
     * @param <T>
     */
    public static <T> HClientResponse<T> get(String url, HClientContext<T> config) throws URISyntaxException {
        return execute("GET", url, config, HttpRequest.BodyPublishers.noBody());
    }

    /**
     * 执行常规的HEAD请求
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @return
     * @throws URISyntaxException
     */
    public static HClientResponse<Void> head(String url, HClientContext<String> config) throws URISyntaxException{
        if(!StringUtils.hasLength(url)){
            throw new IllegalArgumentException("请求地址不可用");
        }
        HttpClient client = buildClient(config);
        HttpRequest request = HttpRequest.newBuilder()
                .method("HEAD", HttpRequest.BodyPublishers.noBody())
                .uri(new URI(contactQueryString(url, config.getQueryString())))
                .timeout(config.getRequestTimeOut())
                .build();
        try {
            HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
            return HClientResponse.success(response.statusCode(), null);
        }catch (IOException|InterruptedException e){
            return config.getExceptionHander().apply(e);
        }
    }

    /**
     * 执行常规同步Form POST请求
     *
     * @param url 请求url
     * @param postForm 表单值
     * @return
     * @throws URISyntaxException
     */
    public static HClientResponse<String> post(String url, Map<String, String> postForm) throws URISyntaxException {
        HClientContext<String> HClientContext = com.apobates.jforum.grief.aurl.executor.HClientContext.Builder.<String>of(1).contentType("application/x-www-form-urlencoded").bodyHandler(HttpResponse.BodyHandlers.ofString()).build();
        return post(url, HClientContext, HttpRequest.BodyPublishers.ofString(getFormDataAsString(postForm, StandardCharsets.UTF_8)));
    }

    /**
     * 将表单数据转换成字符串
     * @param formData 表单数据
     * @param charset 字符编码
     * @return
     */
    protected static String getFormDataAsString(Map<String, String> formData, Charset charset) {
        StringBuilder formBodyBuilder = new StringBuilder();
        for (Map.Entry<String, String> singleEntry : formData.entrySet()) {
            if (formBodyBuilder.length() > 0) {
                formBodyBuilder.append("&");
            }
            formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), charset));
            formBodyBuilder.append("=");
            formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), charset));
        }
        return formBodyBuilder.toString();
    }

    /**
     * 执行常规的POST请求
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @param bodyPublisher 发送的内容
     * @return
     * @param <T>
     */
    public static <T> HClientResponse<T> post(String url, HClientContext<T> config, HttpRequest.BodyPublisher bodyPublisher) throws URISyntaxException {
        return execute("POST", url, config, bodyPublisher);
    }

    /**
     * 使用请求客户端执行指定的请求
     * @param method 请求的方法
     * @param url 请求的地址
     * @param config 请求上下文配置
     * @param bodyPublisher 请求的内容
     * @return
     * @param <T>
     * @throws URISyntaxException
     */
    public static <T> HClientResponse<T> execute(String method, String url, HClientContext<T> config, HttpRequest.BodyPublisher bodyPublisher) throws URISyntaxException {
        if(!StringUtils.hasLength(url)){
            throw new IllegalArgumentException("[url]请求地址不可用");
        }
        String[] allowMethod = new String[]{"GET", "POST", "HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"};
        if(!List.of(allowMethod).contains(method)){
            throw new IllegalArgumentException("[method]非法的请求方法");
        }
        HttpClient client = buildClient(config);
        HttpRequest.Builder request = HttpRequest.newBuilder()
                .method(method, bodyPublisher)
                .uri(new URI(contactQueryString(url, config.getQueryString())))
                .timeout(config.getRequestTimeOut())
                .header("Content-Type", config.getContentType());
        if(config.getHeaders() != null && config.getHeaders().length > 0){
            request = request.headers(config.getHeaders());
        }
        return execute(client, request.build(), config.isAsync(), config.getResponseTimeOut(), config.getHandler(), config.getExceptionHander());
    }

    /**
     * 使用请求客户端执行指定的请求
     * @param client 请求客户端
     * @param request 请求
     * @param isAsyncRequest 是否是异步请求,true异步/false同步
     * @param responseTimeOut 响应等待的时长
     * @param responseBodyHandler 响应内容处理句柄
     * @param exceptionHandler 请求过程中产生异步的转换句柄
     * @return
     * @param <T>
     */
    private static <T> HClientResponse<T> execute(
            HttpClient client,
            HttpRequest request,
            boolean isAsyncRequest,
            Duration responseTimeOut,
            HttpResponse.BodyHandler<T> responseBodyHandler,
            Function<Exception,HClientResponse> exceptionHandler){
        try {
            if(!isAsyncRequest) {
                HttpResponse<T> res = client.send(request, responseBodyHandler);
                return HClientResponse.success(res.statusCode(), res.body());
            }else {
                CompletableFuture<HttpResponse<T>> res = client.sendAsync(request, responseBodyHandler);
                try {
                    return res.thenApply(ele -> HClientResponse.success(ele.statusCode(), ele.body())).get(responseTimeOut.getSeconds(), TimeUnit.SECONDS); // .get(5, TimeUnit.SECONDS);
                }catch (ExecutionException | TimeoutException ex){
                    return exceptionHandler.apply(ex);
                }
            }
        }catch (IOException|InterruptedException e){
            return exceptionHandler.apply(e);
        }
    }

    /**
     * 绑定请求地址的查询参数
     * @param url 请求地址
     * @param queryString 查询参数
     * @return
     */
    protected static String contactQueryString(String url, String queryString){
        if(!queryString.isEmpty()){ // 有查询参数
            String contactChar = url.contains("?")?"&":"?";
            return url.concat(contactChar).concat(queryString);
        }
        return url;
    }

}
